package com.itclj.file;

import static io.netty.buffer.Unpooled.copiedBuffer;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import com.alibaba.fastjson.JSON;
import com.itclj.common.base.RequestData;
import com.itclj.common.base.ResponseData;
import com.itclj.common.exception.LogicException;
import com.itclj.common.util.AuthUtil;
import com.itclj.common.util.ImageUtil;
import com.itclj.common.util.PropertyUtil;
import com.itclj.common.util.RedisUtil;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.DiskAttribute;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.handler.codec.http.multipart.MixedAttribute;
import io.netty.util.CharsetUtil;

public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObject> {

	private static final Logger logger = Logger.getLogger(HttpUploadServerHandler.class.getName());

	private HttpRequest request;

	private boolean readingChunks;

	private final StringBuilder responseContent = new StringBuilder();

	private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);

	private HttpPostRequestDecoder decoder;

	// 请求参数
	private Map<String, String> params = new HashMap<String, String>();

	static {
		DiskFileUpload.deleteOnExitTemporaryFile = true;
		DiskFileUpload.baseDirectory = null; // system temp directory
		DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on
														// exit (in normal exit)
		DiskAttribute.baseDirectory = null; // system temp directory
	}

	@Override
	public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
		if (decoder != null) {
			decoder.cleanFiles();
		}
	}

	@Override
	public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
		if (msg instanceof HttpRequest) {
			HttpRequest request = this.request = (HttpRequest) msg;
			// 鉴权
			try {
				AuthUtil.check(request.headers().get("username"), request.headers().get("password"));
			} catch (LogicException e1) {
				responseContent
						.append(JSON.toJSONString(new ResponseData(ResponseData.STATE.ERROR, e1.getMessage(), "")));
				writeResponse(ctx.channel());
				ctx.channel().close();
				return;
			}

			URI uri = new URI(request.uri());
			if (!uri.getPath().startsWith("/upload")) {
				writeMenu(ctx);
				return;
			}
			responseContent.setLength(0);

			QueryStringDecoder decoderQuery = new QueryStringDecoder(request.uri());
			Map<String, List<String>> uriAttributes = decoderQuery.parameters();
			for (Entry<String, List<String>> attr : uriAttributes.entrySet()) {
				for (String attrVal : attr.getValue()) {
					responseContent.append("URI: " + attr.getKey() + '=' + attrVal + "\r\n");
				}
			}

			if (request.method().equals(HttpMethod.GET)) {
				responseContent.append("\r\n\r\nEND OF GET CONTENT\r\n");
				return;
			}

			try {
				decoder = new HttpPostRequestDecoder(factory, request);
			} catch (ErrorDataDecoderException e1) {
				logger.log(Level.WARNING, e1.getMessage());
				responseContent.append(e1.getMessage());
				writeResponse(ctx.channel());
				ctx.channel().close();
				return;
			}

			readingChunks = HttpUtil.isTransferEncodingChunked(request);
			if (readingChunks) {
				responseContent.append("Chunks: ");
				readingChunks = true;
			}
		}

		if (decoder != null) {
			if (msg instanceof HttpContent) {
				HttpContent chunk = (HttpContent) msg;
				try {
					decoder.offer(chunk);
				} catch (ErrorDataDecoderException e1) {
					logger.log(Level.WARNING, e1.getMessage());
					responseContent.append(e1.getMessage());
					writeResponse(ctx.channel());
					ctx.channel().close();
					return;
				}
				readHttpDataChunkByChunk();
				if (chunk instanceof LastHttpContent) {
					writeResponse(ctx.channel());
					readingChunks = false;

					reset();
				}
			}
		} else {
			writeResponse(ctx.channel());
		}
	}

	private void reset() {
		request = null;
		decoder.destroy();
		decoder = null;
	}

	/**
	 * Example of reading request by chunk and getting values from chunk to
	 * chunk
	 */
	private void readHttpDataChunkByChunk() {
		try {
			while (decoder.hasNext()) {
				InterfaceHttpData data = decoder.next();
				if (data != null) {
					try {
						writeHttpData(data);
					} finally {
						data.release();
					}
				}
			}
		} catch (EndOfDataDecoderException e1) {
			// end
			responseContent.append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n");
		}
	}

	private void writeHttpData(InterfaceHttpData data) {
		if (data.getHttpDataType() == HttpDataType.Attribute) {
			if (data instanceof MixedAttribute) {
				MixedAttribute attribute = (MixedAttribute) data;
				try {
					params.put(attribute.getName(), attribute.getValue());
				} catch (IOException e) {
					logger.log(Level.WARNING, "Error while reading value:" + e.getMessage());
					responseContent.append("\r\nBODY MixedAttribute: " + attribute.getHttpDataType().name() + ": "
							+ attribute.getName() + " Error while reading value: " + e.getMessage() + "\r\n");
					return;
				}
			} else {
				Attribute attribute = (Attribute) data;
				try {
					params.put(attribute.getName(), attribute.getValue());
				} catch (IOException e1) {
					logger.log(Level.WARNING, "Error while reading value:" + e1.getMessage());
					responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": "
							+ attribute.getName() + " Error while reading value: " + e1.getMessage() + "\r\n");
					return;
				}
			}
		} else {
			if (data.getHttpDataType() == HttpDataType.FileUpload) {
				FileUpload fileUpload = (FileUpload) data;
				if (fileUpload.isCompleted()) {
					try {
						String fileName = RandomStringUtils.randomAlphanumeric(32)
								+ fileUpload.getFilename().substring(fileUpload.getFilename().lastIndexOf("."));
						StringBuilder fullPath = new StringBuilder();
						fullPath.append(PropertyUtil.getValue("file.upload.rootPath"));

						String relative = "";
						if (request.uri().startsWith("/upload/img")) {
							relative += "/image";
						} else if (request.uri().startsWith("/upload/audio")) {
							relative += "/audio";
						} else if (request.uri().startsWith("/upload/video")) {
							relative += "/video";
						}
						String path = params.get("path");
						if (StringUtils.isNotBlank(path)) {
							if (!StringUtils.startsWith(path, "/")) {
								relative += "/";
							}
							relative += path;
						}
						relative += "/";
						relative += fileName;
						fullPath.append(relative);

						File f = new File(fullPath.toString());
						File f2 = f.getParentFile();

						// 检查目录是否存在，不存在就新建
						if (!f2.isDirectory()) {
							f2.mkdirs();
						}

						fileUpload.renameTo(f);

						// 图片上传
						if (request.uri().startsWith("/upload/img")) {

							// 图片是否压缩 1:需要压缩 0：不需要压缩
							if (RequestData.ImgCompress.YES.equals(params.get("imgCompress"))) {
								// 图片宽度
								int imgWidth = 0;
								if (NumberUtils.isCreatable(params.get("imgWidth"))) {
									imgWidth = Integer.parseInt(params.get("imgWidth"));
								}

								// 图片高度
								int imgHeight = 0;
								if (NumberUtils.isCreatable(params.get("imgHeight"))) {
									imgHeight = Integer.valueOf(params.get("imgHeight"));
								}
								String imgRatio = params.get("imgRatio");
								ImageUtil.compress(fullPath.toString(), imgRatio, imgWidth, imgHeight);
							}

						}
						// 音频上传
						else if (request.uri().startsWith("/upload/audio")) {

						}
						// 视屏上传
						else if (request.uri().startsWith("/upload/video")) {

						}

						String fileUrl = PropertyUtil.getValue("file.server.url") + relative;

						// 检查是否已经存在该文件，存在了就直接删掉，返回原来的文件URI
						decoder.removeHttpDataFromClean(fileUpload);
						InputStream isFile = new FileInputStream(f);
						String md5key = DigestUtils.md5Hex(isFile);
						isFile.close();

						if (StringUtils.isBlank(RedisUtil.get(md5key))) {
							RedisUtil.set(md5key, fileUrl);
						} else if (f.exists()) {
							f.delete();
						}

						responseContent.append(JSON.toJSONString(
								new ResponseData(ResponseData.STATE.SUCESS, "操作成功", RedisUtil.get(md5key))));

					} catch (IOException e1) {
						logger.log(Level.WARNING, e1.getMessage());
					}
				} else {
					responseContent.append("\tFile to be continued but should not!\r\n");
				}
			}
		}

	}

	private void writeResponse(Channel channel) {
		// Convert the response content to a ChannelBuffer.
		ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
		responseContent.setLength(0);

		// Decide whether to close the connection or not.
		boolean close = HttpHeaderValues.CLOSE
				.contentEqualsIgnoreCase(request.headers().get(HttpHeaderNames.CONNECTION))
				|| request.protocolVersion().equals(HttpVersion.HTTP_1_0) && !HttpHeaderValues.KEEP_ALIVE
						.contentEqualsIgnoreCase(request.headers().get(HttpHeaderNames.CONNECTION));

		// Build the response object.
		FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
		response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");

		if (!close) {
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
		}

		Set<Cookie> cookies;
		String value = request.headers().get(HttpHeaderNames.COOKIE);
		if (value == null) {
			cookies = Collections.emptySet();
		} else {
			cookies = ServerCookieDecoder.STRICT.decode(value);
		}
		if (!cookies.isEmpty()) {
			// Reset the cookies if necessary.
			for (Cookie cookie : cookies) {
				response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
			}
		}
		// Write the response.
		ChannelFuture future = channel.writeAndFlush(response);
		// Close the connection after the write operation is done if necessary.
		if (close) {
			future.addListener(ChannelFutureListener.CLOSE);
		}
	}

	private void writeMenu(ChannelHandlerContext ctx) {
		// print several HTML forms
		// Convert the response content to a ChannelBuffer.
		responseContent.setLength(0);

		// create Pseudo Menu
		responseContent.append("<html>");
		responseContent.append("<head>");
		responseContent.append("<title>Netty Test upload</title>\r\n");
		responseContent.append("</head>\r\n");
		responseContent.append("<body bgcolor=white><style>td{font-size: 12pt;}</style>");

		responseContent.append("<table border=\"0\">");
		responseContent.append("<tr>");
		responseContent.append("<td>");
		responseContent.append("<h1>Netty Test upload</h1>");
		responseContent.append("Choose one FORM");
		responseContent.append("</td>");
		responseContent.append("</tr>");
		responseContent.append("</table>\r\n");

		// POST with enctype="multipart/form-data"
		responseContent.append("<CENTER>普通上传<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
		responseContent.append("<FORM ACTION=\"/upload\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">");
		responseContent.append("<table border=\"0\">");
		responseContent.append("<tr><td>file path: <br> <input type=text name=\"path\" size=50></td></tr>");
		responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
		responseContent.append("<tr><td>Fill with value: <br> ");
		responseContent.append("<textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
		responseContent.append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">");
		responseContent.append("</td></tr>");
		responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
		responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
		responseContent.append("</table></FORM>\r\n");
		responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");

		// 图片上传
		responseContent.append("<CENTER>图片上传<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
		responseContent.append("<FORM ACTION=\"/upload/img\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">");
		responseContent.append("<table border=\"0\">");
		responseContent.append("<tr><td>file path: <br> <input type=text name=\"path\" size=50></td></tr>");
		responseContent.append("<tr><td>图片是否压缩 1:需要压缩 0：不需要压缩 <br> ");
		responseContent.append("<input type=text name=\"imgCompress\" size=20>");
		responseContent.append("</td></tr>");
		responseContent.append("<tr><td>图片是否等比例压缩，1：等比例压缩，0：不等比例压缩 <br> ");
		responseContent.append("<input type=text name=\"imgRatio\" size=20>");
		responseContent.append("</td></tr>");
		responseContent.append("<tr><td>图片宽度 <br> ");
		responseContent.append("<input type=text name=\"imgWidth\" size=20>");
		responseContent.append("</td></tr>");
		responseContent.append("<tr><td>图片高度 <br> ");
		responseContent.append("<input type=text name=\"imgHeight\" size=20>");
		responseContent.append("</td></tr>");
		responseContent.append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">");
		responseContent.append("</td></tr>");
		responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
		responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
		responseContent.append("</table></FORM>\r\n");
		responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");

		// 音频上传
		responseContent.append("<CENTER>音频上传<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
		responseContent.append("<FORM ACTION=\"/upload/audio\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">");
		responseContent.append("<table border=\"0\">");
		responseContent.append("<tr><td>file path: <br> <input type=text name=\"path\" size=50></td></tr>");
		responseContent.append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">");
		responseContent.append("</td></tr>");
		responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
		responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
		responseContent.append("</table></FORM>\r\n");
		responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");

		// 视屏上传
		responseContent.append("<CENTER>视屏上传<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
		responseContent.append("<FORM ACTION=\"/upload/video\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">");
		responseContent.append("<table border=\"0\">");
		responseContent.append("<tr><td>file path: <br> <input type=text name=\"path\" size=50></td></tr>");
		responseContent.append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">");
		responseContent.append("</td></tr>");
		responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
		responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
		responseContent.append("</table></FORM>\r\n");
		responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");

		responseContent.append("</body>");
		responseContent.append("</html>");

		ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
		// Build the response object.
		FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);

		response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
		response.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());

		// Write the response.
		ctx.channel().writeAndFlush(response);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		logger.log(Level.WARNING, responseContent.toString(), cause);
		ctx.channel().close();
	}

}
